package org.kwisatz.proxybot;
import org.kwisatz.proxybot.core.*;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Connection
{
	private final static Charset CHARSET = Charset.forName("ISO-8859-1");
	
	private class IRCPrintWriter extends PrintWriter
	{
		private String type;

		IRCPrintWriter(OutputStream out, String type)
		{
			super(new OutputStreamWriter(out, CHARSET));
			this.type=type;
		}
			
		@Override
		public void write(String s)
		{		
			super.write(s);
			if(logOut!=null)
				logOut.print(type+s);
		}
		
		@Override
		public void println()
		{
			super.write("\r\n");
			flush();
			if(logOut!=null)
				logOut.println();
		}	
	}
	
	private Socket socketClient;
	private PrintStream logOut;
	private Server server;
	private static final Pattern p=Pattern.compile("CONNECT +([^ :]+)(?::([0-9]+))? +HTTP/.+");
	private final RootFilter rootFilter=new RootFilter();
	private String host;
	private int port;
	
	public Connection(Server server, Socket s) throws IOException
	{
		this.server = server;
		socketClient=s;
		String logFile = System.getProperty("proxybot.logFile");
		if(logFile != null)
			logOut=new PrintStream(new FileOutputStream(logFile));
		init();
	}
	
	private void init()
	{
		try
		{
			Main.LOGGER.fine("Waiting for CONNECT line");
			BufferedReader clientIn=new BufferedReader(new InputStreamReader(
				socketClient.getInputStream(), CHARSET));
			String line=clientIn.readLine();
			clientIn.readLine();
			Logger.getAnonymousLogger().fine("received: "+line);
			Matcher m = p.matcher(line);
			if(m.matches())
			{
				new PrintStream(socketClient.getOutputStream()).print(
						"HTTP/1.0 200 Connection established\r\n\r\n");
				port=80;
				host=m.group(1);
				if(m.groupCount()>1)
					port=Integer.parseInt(m.group(2));
				
				Socket socketServer=new Socket(createProxy());
				socketServer.connect(new InetSocketAddress(host, port));				
				
				BufferedReader serverIn=new BufferedReader(
					new InputStreamReader(socketServer.getInputStream(), CHARSET));
				PrintWriter serverOut=new IRCPrintWriter(socketServer.getOutputStream(), "TO SERV: ");
				PrintWriter clientOut=new IRCPrintWriter(socketClient.getOutputStream(), "TO CLNT: ");
				Pipe pServ=new Pipe(serverIn, clientOut)
				{
					@Override
					protected void closing() {
						server.remove(Connection.this);
					}
				};
				Pipe pCli=new Pipe(clientIn, serverOut)
				{
					@Override
					protected void closing() {
						server.remove(Connection.this);
					}					
				};
				
				Context context=new ContextImpl(rootFilter, socketServer, serverOut, clientOut);
				rootFilter.setContext(context);
				
				pServ.setListener(new Pipe.Filter()
				{
					public String handleLine(String line)
					{						
						String fline=line;
						Message m = Parser.parse(line);
						if(m != null)
						{
							fline=rootFilter.handleServerLine(line, m);
							if(logOut!=null)
							{
								logOut.println("FROM SV: "+line);
								logOut.println("FILT SV: "+fline);
							}
						}
						return fline;
					}
				});
				
				pCli.setListener(new Pipe.Filter()
				{
					public String handleLine(String line)
					{
						String fline=line;
						Message m = Parser.parse(line);
						if(m != null)
						{
							fline=rootFilter.handleClientLine(line, m);
							if(logOut!=null)
							{
								logOut.println("FROM CL: "+line);
								logOut.println("FILT CL: "+fline);
							}
						}
						return fline;
					}
				});

				pServ.start();
				pCli.start();
			}
			else
				new PrintStream(socketClient.getOutputStream()).print("Valid CONNECT command expected\r\n");
		}
		catch(IOException ex)
		{
			ex.printStackTrace();
		}		
	}

	//TODO use java.net.Authenticator for proxy authentication
	private static Proxy createProxy()
	{
		Proxy.Type type=null;
		int port = 0;
		String host = System.getProperty("proxybot.proxy.host");
		String typeString = System.getProperty("proxybot.proxy.type");

		if(typeString == null)
			return Proxy.NO_PROXY;
		
		try
		{
			type=Proxy.Type.valueOf(typeString );
			port = Integer.parseInt(""+System.getProperty("proxybot.proxy.port"));
			return new Proxy(type, new InetSocketAddress(host, port));
		}
		catch(NumberFormatException ex)
		{
			Main.LOGGER.severe("Invalid proxy port in configuration file");
			return Proxy.NO_PROXY;
		}
		catch(IllegalArgumentException ex)
		{
			Main.LOGGER.severe("Invalid proxy type in configuration file:"+
				" proxy.type={DIRECT|SOCKS|HTTP}");
			return Proxy.NO_PROXY;
		}		
	}

	public void add(Class<? extends Filter> filterClass)
		throws InstantiationException, IllegalAccessException
	{
		rootFilter.add(filterClass);
	}

	public void remove(String id)
	{
		rootFilter.remove(id);
	}

	public Map<String, Filter> getFilters()
	{
		return rootFilter.getFilters();
	}

	@Override
	public String toString() {
		return host+":"+port;
	}
}
